<template>
	<div class="scrollbar__track" ref="track" @mousedown="handleTrackMousedown">
		<div class="scrollbar__thumb" ref="thumb" @mousedown="handleThumbMousedown"></div>
	</div>
</template>

<script>
const BAR_MAP = {
	vertical: {
		name: 'vertical',
		offset: 'offsetTop',
		clientSize: 'clientHeight',
		scroll: 'scrollTop',
		scrollSize: 'scrollHeight',
		thumbSize: 'height',
		client: 'clientY',
		position: 'top',
	},
	horizontal: {
		name: 'horizontal',
		offset: 'offsetLeft',
		clientSize: 'clientWidth',
		scroll: 'scrollLeft',
		scrollSize: 'scrollWidth',
		thumbSize: 'width',
		client: 'clientX',
		position: 'left',
	},
}

export default {
	props: {
		// 垂直滚动/水平滚动
		vertical: {
			type: Boolean,
			default: true,
		},
		// 滚动条粗细
		size: {
			type: Number,
			default: 6,
		},
		// 滚动条颜色
		color: {
			type: String,
			default: '#939393',
		},
		// 内容滚动的距离
		scroll: {
			type: Number,
			default: 0,
		},
		// 可视区大小
		clientSize: {
			type: Number,
			default: 0,
		},
		// 内容真实大小
		scrollSize: {
			type: Number,
			default: 0,
		},
	},
	data() {
		return {
			// 滚动轨道
			track: null,
			// 滚动条
			thumb: null,
			// 滚动条长度
			thumbSize: 0,
			// 滚动速率
			speed: 0,
			// 鼠标按下时，光标离滚动条顶部的距离加上滚动区离浏览器顶部的距离
			record: 0,
			bar: BAR_MAP[this.vertical ? 'vertical' : 'horizontal'],
		}
	},
	watch: {
		// 内容区滚动的时候带动滚动条移动
		scroll(value) {
			this.setThumbPosition()
		},
		clientSize() {
			this.handleUpdate()
		},
		scrollSize() {
			this.handleUpdate()
		},
	},
	mounted() {
		this.track = this.$refs.track
		this.thumb = this.$refs.thumb
		this.initTrackStyle()
		this.initThumbStyle()
		this.handleUpdate()
	},
	destroyed() {
		document.removeEventListener('mouseup', this.handleDocumentMouseup)
	},
	methods: {
		// 初始化滚动轨道样式
		initTrackStyle() {
			const { track, size, vertical, bar } = this
			if (vertical) {
				track.style.width = size + 'px'
				track.style.right = '0px'
			} else {
				track.style.height = size + 'px'
				track.style.bottom = '0px'
			}
		},
		// 初始化滚动条样式
		initThumbStyle() {
			const { thumb, color, size, vertical } = this
			thumb.style.backgroundColor = color
			thumb.style.borderRadius = size / 2 + 'px'
			if (vertical) {
				thumb.style.width = size + 'px'
			} else {
				thumb.style.height = size + 'px'
			}
		},
		// 设置滚动条长度
		setThumbSize() {
			const { scrollSize, clientSize, bar } = this
			if (scrollSize > clientSize) {
				this.thumbSize = (clientSize * clientSize) / scrollSize
			} else {
				this.thumbSize = 0
			}
			this.thumb.style[bar.thumbSize] = this.thumbSize + 'px'
		},
		// 设置滚动条位置
		setThumbPosition() {
			const { thumb, scroll, speed, bar } = this
			thumb.style[bar.position] = scroll / speed + 'px'
		},
		// 设置滚动速率
		setScrollSpeed() {
			const { scrollSize, clientSize, thumbSize } = this
			this.speed = (scrollSize - clientSize) / (clientSize - thumbSize)
		},
		handleUpdate() {
			this.setThumbSize()
			this.setScrollSpeed()
			this.setThumbPosition()
		},
		// 控制内容区的滚动
		handleContentScroll(position) {
			let end = position
			const { clientSize, thumbSize, speed, bar } = this
			const max = clientSize - thumbSize
			if (end < 0) {
				// 滚动条到达最顶部
				end = 0
			} else if (end > max) {
				// 滚动条到达最底部
				end = max
			}
			this.$emit('onManualScroll', end * speed, bar.scroll)
		},
		// 拖动滚动条进行滚动
		handleThumbMousedown(event) {
			event.stopPropagation()
			// 原生滚动条不能按住右键进行滚动
			if (event.button === 2) {
				return
			}
			this.record = event[this.bar.client] - this.thumb[this.bar.offset]
			document.addEventListener('mousemove', this.handleDocumentMousemove)
			document.addEventListener('mouseup', this.handleDocumentMouseup)
		},
		handleDocumentMousemove(event) {
			const position = event[this.bar.client] - this.record
			this.handleContentScroll(position)
			// 防止拖动太快选中文字
			window.getSelection().removeAllRanges()
		},
		handleDocumentMouseup() {
			document.removeEventListener('mousemove', this.handleDocumentMousemove)
		},
		// 点击滚动轨道进行滚动
		handleTrackMousedown(event) {
			if (event.button === 2) {
				return
			}
			// 浏览器原生滚动条是点一下，则滚动一定距离，这里直接滚动到点击的位置。
			const position =
				event[this.bar.client] - event.target.getBoundingClientRect()[this.bar.position] - this.thumbSize / 2
			this.handleContentScroll(position)
		},
	},
}
</script>

<style lang="scss" scoped>
.scrollbar__track {
	position: absolute;
	width: 100%;
	height: 100%;

	.scrollbar__thumb {
		position: absolute;
		width: 100%;
		height: 100%;
		cursor: pointer;
		opacity: 0.4;

		&:hover {
			opacity: 0.6;
		}
	}
}
</style>
